/**
 * Title: jFamilyTree
 * Description: jFamilyTree mutable
 * Version: provider
 * Copyright: (c) 2001-2008
 * Terms of use:see license agreement at http://treebolic.sourceforge.net/en/license.htm
 * Author: Bernard Bou
 * Company: bsys
 * Update: Mon Mar 10 00:00:00 CEST 2008
 */
package jFamilyTree.model;

import java.awt.Color;
import java.io.IOException;
import java.net.URL;
import java.util.Properties;

import jFamilyTree.control.Controller;

/**
 * Attribute utilities
 * 
 * @author Bernard Bou
 */
public class Utils
{
	// S T Y L E

	/**
	 * Style component pointer
	 */
	static public enum StyleComponent
	{
		STROKE, FROMTERMINATOR, TOTERMINATOR, HIDDEN
	};

	// 0000 0000 ttttt tttt ffff ffff 0000 sssh
	// 0000 0000 0tttt 0000 0fff 0000 0000 dDsh

	/**
	 * Parse edge style
	 * 
	 * @param thisStroke
	 *        stroke
	 * @param thisFromTerminator
	 *        from-terminator
	 * @param thisToTerminator
	 *        to-terminator
	 * @param thisHiddenFlag
	 *        whether edge is hidden ('true' or 'false')
	 * @return style
	 */
	static public Integer parseStyle(final String thisStroke, final String thisFromTerminator, final String thisToTerminator, final String thisHiddenFlag)
	{
		int thisStyle = 0;
		if (thisHiddenFlag != null && Boolean.valueOf(thisHiddenFlag).booleanValue())
			return IEdge.HIDDEN;

		if (thisStroke != null && !thisStroke.isEmpty())
		{
			thisStyle |= stringToStroke(thisStroke);
		}
		if (thisFromTerminator != null && !thisFromTerminator.isEmpty())
		{
			thisStyle |= stringToShape(thisFromTerminator) << IEdge.FROMSHIFT;
			thisStyle |= stringToFill(thisFromTerminator) << IEdge.FROMSHIFT;
		}
		if (thisToTerminator != null && !thisToTerminator.isEmpty())
		{
			thisStyle |= stringToShape(thisToTerminator) << IEdge.TOSHIFT;
			thisStyle |= stringToFill(thisToTerminator) << IEdge.TOSHIFT;
		}
		return thisStyle == 0 ? null : thisStyle;
	}

	/**
	 * Modify style
	 * 
	 * @param thisStyle
	 *        current style
	 * @param thisValue
	 *        string value
	 * @param thisComponent
	 *        style component to modify
	 * @return modified style
	 */
	static public Integer modifyStyle(Integer thisStyle, final Object thisValue, StyleComponent thisComponent)
	{
		if (thisStyle == null && thisValue == null)
			return null;
		if (thisStyle == null)
			thisStyle = 0;

		switch (thisComponent)
		{
		case HIDDEN:
		{
			Boolean thisBooleanValue = (Boolean) thisValue;
			thisStyle &= ~IEdge.HIDDEN;
			if (thisBooleanValue != null && thisBooleanValue)
				thisStyle |= IEdge.HIDDEN;
			return thisStyle;
		}

		case STROKE:
		{
			String thisStringValue = (String) thisValue;
			thisStyle &= ~IEdge.STROKEMASK;
			if (thisStringValue != null && !thisStringValue.isEmpty())
				thisStyle |= stringToStroke(thisStringValue);
			return thisStyle;
		}

		case FROMTERMINATOR:
		{
			String thisStringValue = (String) thisValue;
			thisStyle &= ~IEdge.FROMMASK;
			if (thisStringValue != null && !thisStringValue.isEmpty())
			{
				thisStyle |= stringToShape(thisStringValue) << IEdge.FROMSHIFT;
				thisStyle |= stringToFill(thisStringValue) << IEdge.FROMSHIFT;
			}
			return thisStyle;
		}

		case TOTERMINATOR:
		{
			String thisStringValue = (String) thisValue;
			thisStyle &= ~IEdge.TOMASK;
			if (thisStringValue != null && !thisStringValue.isEmpty())
			{
				thisStyle |= stringToShape(thisStringValue) << IEdge.TOSHIFT;
				thisStyle |= stringToFill(thisStringValue) << IEdge.TOSHIFT;
			}
			return thisStyle;
		}
		}
		return null;
	}

	/**
	 * Stringify edge style component
	 * 
	 * @param thisStyle
	 *        style
	 * @param thisComponent
	 *        part of style to stringify
	 * @return string
	 */
	static public String toString(final Integer thisStyle, StyleComponent thisComponent)
	{
		if (thisStyle == null)
			return null;

		switch (thisComponent)
		{
		case HIDDEN:
		{
			return (thisStyle & IEdge.HIDDEN) != 0 ? "true" : "false";
		}
		case STROKE:
		{
			return strokeToString(thisStyle);
		}
		case FROMTERMINATOR:
		{
			int n = thisStyle & IEdge.FROMMASK;
			n >>= IEdge.FROMSHIFT;
			return shapeToString(n) + fillToString(n);
		}
		case TOTERMINATOR:
		{
			int n = thisStyle & IEdge.TOMASK;
			n >>= IEdge.TOSHIFT;
			return shapeToString(n) + fillToString(n);
		}

		default:
			return null;
		}
	}

	/**
	 * Stringify edge style component
	 * 
	 * @param thisStyle
	 *        style
	 * @return hidden, stroke, fromterminator, toterminator strings
	 */
	static public String[] toStrings(final Integer thisStyle)
	{
		return new String[] { //
		toString(thisStyle, StyleComponent.HIDDEN), //
				toString(thisStyle, StyleComponent.STROKE), //
				toString(thisStyle, StyleComponent.FROMTERMINATOR), //
				toString(thisStyle, StyleComponent.TOTERMINATOR), };
	}

	// hidden

	/**
	 * Convert to boolean (true,null)
	 * 
	 * @param thisStyle
	 *        style
	 * @param thisComponent
	 *        part of style to convert
	 * @return true or null
	 */
	static public Boolean toTrueBoolean(final Integer thisStyle, StyleComponent thisComponent)
	{
		if (thisStyle == null)
			return null;

		switch (thisComponent)
		{
		case HIDDEN:
		{
			return (thisStyle & IEdge.HIDDEN) != 0 ? Boolean.TRUE : null;
		}
		default:
			return null;
		}
	}

	// stroke

	/**
	 * Convert string to stroke code
	 * 
	 * @param thisString
	 *        stroke string
	 * @return stroke code style
	 */
	public static int stringToStroke(final String thisString)
	{
		int thisStyle = 0;
		if (thisString != null)
		{
			if (thisString.equals("solid"))
				thisStyle |= IEdge.SOLID;
			else if (thisString.equals("dash"))
				thisStyle |= IEdge.DASH;
			else if (thisString.equals("dot"))
				thisStyle |= IEdge.DOT;
		}
		return thisStyle;
	}

	/**
	 * Convert stroke code to string
	 * 
	 * @param thisStyle
	 *        stroke code style
	 * @return stroke string
	 */
	public static String strokeToString(final Integer thisStyle)
	{
		if (thisStyle == null)
			return "";
		int n = thisStyle & IEdge.STROKEMASK;
		switch (n)
		{
		case IEdge.SOLID:
			return "solid";
		case IEdge.DASH:
			return "dash";
		case IEdge.DOT:
			return "dot";
		}
		return "";
	}

	// fill

	/**
	 * Convert string to fill code style
	 * 
	 * @param thisString
	 *        fill string
	 * @return fill code style
	 */
	public static int stringToFill(final String thisString)
	{
		if (thisString.length() > 1 && thisString.charAt(1) == 'f')
			return IEdge.SOLID;
		return 0;
	}

	/**
	 * Convert string to fill code style
	 * 
	 * @param thisStyle
	 *        fill code style
	 * @return fill string
	 */
	public static String fillToString(final Integer thisStyle)
	{
		if (thisStyle == null)
			return "";
		return (thisStyle & IEdge.SOLID) != 0 ? "f" : "";
	}

	// shape

	/**
	 * Convert string to shape code style
	 * 
	 * @param thisString
	 *        shape string
	 * @return shape code style
	 */
	public static int stringToShape(final String thisString)
	{
		switch (thisString.charAt(0))
		{
		case 'a':
			return IEdge.ARROW;
		case 'h':
			return IEdge.HOOK;
		case 'c':
			return IEdge.CIRCLE;
		case 'd':
			return IEdge.DIAMOND;
		case 't':
			return IEdge.TRIANGLE;
		case 'z':
		default:
			return 0;
		}
	}

	/**
	 * Convert shape code style to string
	 * 
	 * @param thisStyle
	 *        shape code style
	 * @return shape string
	 */
	public static String shapeToString(final Integer thisStyle)
	{
		if (thisStyle == null)
			return "";
		int n = thisStyle & IEdge.SHAPEMASK;
		switch (n)
		{
		case IEdge.ARROW:
			return "a";
		case IEdge.HOOK:
			return "h";
		case IEdge.CIRCLE:
			return "c";
		case IEdge.DIAMOND:
			return "d";
		case IEdge.TRIANGLE:
			return "t";
		case 0:
			return "";
		default:
			return "?";
		}
	}

	// M E N U I T E M

	/**
	 * Parse strings and set menuitem fields accordingly
	 * 
	 * @param thisMenuItem
	 *        menuitem
	 * @param thisActionString
	 *        action string
	 * @param thisScopeString
	 *        scope string
	 * @param thisModeString
	 *        mode string
	 */
	static public void parseMenuItem(final MenuItem thisMenuItem, final String thisActionString, final String thisScopeString, final String thisModeString)
	{
		thisMenuItem.theAction = stringToAction(thisActionString);
		thisMenuItem.theMatchScope = stringToScope(thisScopeString);
		thisMenuItem.theMatchMode = stringToMode(thisModeString);
	}

	/**
	 * Parse menuitem action
	 * 
	 * @param thisActionString
	 *        action string
	 * @return action
	 */
	static public Controller.Action stringToAction(final String thisActionString)
	{
		if (thisActionString != null)
		{
			if (thisActionString.equalsIgnoreCase("goto"))
				return Controller.Action.GOTO;
			else if (thisActionString.equalsIgnoreCase("search"))
				return Controller.Action.SEARCH;
			else if (thisActionString.equalsIgnoreCase("focus"))
				return Controller.Action.FOCUS;
		}
		return null;
	}

	/**
	 * Parse menuitem scope
	 * 
	 * @param thisScopeString
	 *        scope string
	 * @return scope
	 */
	static public Controller.MatchScope stringToScope(final String thisScopeString)
	{
		if (thisScopeString != null)
		{
			if (thisScopeString.equalsIgnoreCase("label"))
				return Controller.MatchScope.LABEL;
			else if (thisScopeString.equalsIgnoreCase("content"))
				return Controller.MatchScope.CONTENT;
			else if (thisScopeString.equalsIgnoreCase("link"))
				return Controller.MatchScope.LINK;
			else if (thisScopeString.equalsIgnoreCase("id"))
				return Controller.MatchScope.ID;
		}
		return Controller.MatchScope.ID;
	}

	/**
	 * Parse menuitem mode
	 * 
	 * @param thisModeString
	 *        mode string
	 * @return mode
	 */
	static public Controller.MatchMode stringToMode(final String thisModeString)
	{
		if (thisModeString != null)
		{
			if (thisModeString.equalsIgnoreCase("equals"))
				return Controller.MatchMode.EQUALS;
			else if (thisModeString.equalsIgnoreCase("startswith"))
				return Controller.MatchMode.STARTSWITH;
			else if (thisModeString.equalsIgnoreCase("includes"))
				return Controller.MatchMode.INCLUDES;
		}
		return Controller.MatchMode.INCLUDES;
	}

	/**
	 * Stringify action
	 * 
	 * @param thisAction
	 *        action
	 * @return action string
	 */
	static public String toString(final Controller.Action thisAction)
	{
		if (thisAction != null)
			switch (thisAction)
			{
			case GOTO:
				return "goto";
			case SEARCH:
				return "search";
			case FOCUS:
				return "focus";
			case MOUNT:
				return "mount";
			}
		return null;
	}

	/**
	 * Stringify scope
	 * 
	 * @param thisScope
	 *        scope
	 * @return scope string
	 */
	static public String toString(final Controller.MatchScope thisScope)
	{
		if (thisScope != null)
			switch (thisScope)
			{
			case LABEL:
				return "label";
			case CONTENT:
				return "content";
			case LINK:
				return "link";
			case ID:
				return "id";
			}
		return null;
	}

	/**
	 * Stringify mode
	 * 
	 * @param thisMode
	 *        mode
	 * @return mode string
	 */
	static public String toString(final Controller.MatchMode thisMode)
	{
		if (thisMode != null)
			switch (thisMode)
			{
			case EQUALS:
				return "equals";
			case STARTSWITH:
				return "startswith";
			case INCLUDES:
				return "includes";
			}
		return null;
	}

	/**
	 * Parse menuitem action, matchmode, matchscope fields to strings
	 * 
	 * @param thisMenuItem
	 *        menuitem
	 * @return action, matchscope, matchmode strings
	 */
	static public String[] toStrings(final MenuItem thisMenuItem)
	{
		String thisAction = toString(thisMenuItem.theAction);
		String thisScope = toString(thisMenuItem.theMatchScope);
		String thisMode = toString(thisMenuItem.theMatchMode);
		return new String[] { thisAction, thisScope, thisMode };
	}

	// C O L O R

	/**
	 * Convert color to hexadecimal string
	 * 
	 * @param thisColor
	 *        color
	 * @return prefixless hexadecimal representation of color
	 */
	static public String colorToString(final Color thisColor)
	{
		return thisColor == null ? "" : Integer.toHexString(thisColor.getRGB()).substring(2);
	}

	/**
	 * Convert hexadecimal string to color
	 * 
	 * @param thisString
	 *        prefixless hexadecimal representation of color
	 * @return color
	 */
	static public Color stringToColor(String thisString)
	{
		if (thisString == null || thisString.isEmpty())
			return null;
		if (thisString.startsWith("#"))
			thisString = thisString.substring(1);
		try
		{
			return Color.decode("0x" + thisString);
		}
		catch (Exception e)
		{
		}
		return null;
	}
	
	/**
	 * Load properties from URL
	 * 
	 * @param thisUrl
	 *        url of property file to load from
	 * @throws IOException
	 */
	static public Properties load(URL thisUrl) throws IOException
	{
		Properties theseProperties = new Properties();
		theseProperties.load(thisUrl.openStream());
		return theseProperties;
	}
}
